package tp.pr5;

import java.util.Iterator;

import tp.pr5.instructions.exceptions.InstructionExecutionException;
import tp.pr5.items.Item;

/**
 * This class is in charge of the robot navigation features. It contains the
 * city where the robot looks for garbage, the current place where the robot is,
 * and the current direction of the robot. It contains methods to handle the
 * different robot movements and to pick and drop items at the current place.
 * 
 */
public class NavigationModule extends Observable<NavigationObserver>{
	private Place currentPlace;
	private Direction currentHeading;
	private City aCity;
	private enum Action {INIT, MOVE, ROTATE, SCAN};
	
	/**
	 * Navigation module constructor. It needs the city map and the initial place
	 * @param City aCity
	 * @param Place initialPlace
	 */
	public NavigationModule(City aCity, Place initialPlace)
	{
		super();
		this.currentPlace = initialPlace;
		this.aCity = aCity;
		this.currentHeading = Direction.NORTH;
	}

	/**
	 * Checks if the robot has arrived at a spaceship
	 * @return true if an only if the current place is the spaceship
	 */
	public boolean atSpaceship()
	{
		return this.currentPlace.isSpaceship();
	}
	
	/**
	 * Updates the current direction of the robot according to the rotation
	 * @param rotation left or right
	 */
	public void rotate(Rotation rotation)
	{
		this.currentHeading = currentHeading.rotate(rotation);
		notifiesObserver(null, this.currentHeading, Action.ROTATE);
	}
	
	/**
	 * The method tries to move the robot following the current direction. If
	 * the movement is not possible because there is no street, or there is a
	 * street which is closed, then it throws an exception. Otherwise the
	 * current place is updated according to the movement
	 * 
	 * @throws InstructionExecutionException
	 */
	public void move() throws InstructionExecutionException
	{
		Street street = this.getHeadingStreet();
		
		if (street != null)
		{
			if (street.isOpen())
			{	
				this.currentPlace = street.nextPlace(currentPlace);				
				notifiesObserver(this.currentPlace, this.currentHeading, Action.MOVE);
			}
			else
			{
				throw new InstructionExecutionException(Constants.MESSAGE_SAYS + Constants.MESSAGE_DOOR_CLOSED);
			}
		}
		else
		{
			throw new InstructionExecutionException(Constants.MESSAGE_SAYS + Constants.MESSAGE_UNIDIRECTIONAL_STREET + this.getCurrentHeading());
		}
	}

	/**
	 * Tries to pick an item characterized by a given identifier from the
	 * current place. If the action was completed the item is removed from the
	 * current place.
	 * 
	 * @param id The identifier of the item
	 * @return The item of identifier id if it exists in the place. Otherwise
	 *         the method returns null
	 */
	public Item pickItemFromCurrentPlace(String id)
	{
		return this.currentPlace.pickItem(id);
	}
	
	/**
	 * Drop an item in the current place. It assumes that there is no other item
	 * with the same name/id there. Otherwise, the behaviour is undefined.
	 * 
	 * @param item The name of the item to be dropped.
	 */
	public void dropItemAtCurrentPlace(Item item)
	{
		this.currentPlace.dropItem(item);
	}
	
	/**
	 * Checks if there is an item with a given id in this place
	 * @param id Identifier of the item we are looking for
	 * @return true if and only if an item with this id is in the current place
	 */
	public boolean findItemAtCurrentPlace(String id)
	{
		return this.currentPlace.existItem(id);
	}

	/**
	 * Initializes the current heading according to the parameter
	 * @param heading New direction for the robot
	 */
	public void initHeading(Direction heading)
	{
		this.currentHeading = heading;
		notifiesObserver(this.currentPlace, this.currentHeading, Action.INIT);
	}
	
	/**
	 * Provides the observers with the information (description + inventory) of the current place
	 */
	public void scanCurrentPlace()
	{
		notifiesObserver(this.currentPlace, null, Action.SCAN);
	}
	
	/**
	 * Returns the street opposite the robot
	 * 
	 * @return The street which the robot is facing, or null, if there is not
	 *         any street in this direction
	 */
	public Street getHeadingStreet()
	{
		return this.aCity.lookForStreet(this.currentPlace, this.currentHeading);
	}
	
	/**
	 * Returns the robot heading
	 * @return The direction where the robot is facing to.
	 */
	public Direction getCurrentHeading()
	{
		return this.currentHeading;
	}
	
	/**
	 * Returns the current place where the robot is (for testing purposes)
	 * @return The current place
	 */
	public Place getCurrentPlace()
	{
		return this.currentPlace;
	}
	
	private void notifiesObserver(PlaceInfo place, Direction direction, Action action)
	{
		Iterator<NavigationObserver> itr = this.list.iterator();
		while(itr.hasNext()) {
			NavigationObserver element = itr.next();
			switch (action)
			{
				case INIT:
				{
					element.initNavigationModule(place, direction);
					break;
				}
				case MOVE:
				{
					element.placeHasChanged(place);
					element.robotArrivesAtPlace(direction, place);
					break;
				}
				case ROTATE:
				{
					element.headingChanged(direction);
					break;
				}
				case SCAN:
				{
					element.placeScanned(place);
					break;
				}
			}
		}
	}
	
}
